package vb.week1.symtab;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

public class SymbolTable<Entry extends IdEntry> {

	private int currentLevel;
	private Stack<String> stack;
	private Map<Integer, Entry> map;

	/**
	 * Constructor.
	 * 
	 * @ensures this.currentLevel() == -1
	 */
	public SymbolTable() {
		currentLevel = -1;
		stack = new Stack<String>();
		map = new HashMap<Integer, Entry>();
	}

	/**
	 * Opens a new scope.
	 * 
	 * @ensures this.currentLevel() == old.currentLevel()+1;
	 */
	public void openScope() {
		currentLevel++;
	}

	/**
	 * Closes the current scope. All identifiers in the current scope will be
	 * removed from the SymbolTable.
	 * 
	 * @requires old.currentLevel() > -1;
	 * @ensures this.currentLevel() == old.currentLevel()-1;
	 */
	public void closeScope() {
		while (map.size() > 0 && map.get(stack.size()).getLevel() == currentLevel) {
			map.remove(stack.size());
			stack.pop();
		}
		currentLevel--;
	}

	/** Returns the current scope level. */
	public int currentLevel() {
		return currentLevel;
	}

	/**
	 * Enters an id together with an entry into this SymbolTable using the
	 * current scope level. The entry's level is set to currentLevel().
	 * 
	 * @requires id != null && id.length() > 0 && entry != null;
	 * @ensures this.retrieve(id).getLevel() == currentLevel();
	 * @throws SymbolTableException
	 *             when there is no valid current scope level, or when the id is
	 *             already declared on the current level.
	 */
	public void enter(String id, Entry entry) throws SymbolTableException {
		// check for valid scope level
		if (currentLevel < 0)
			throw new SymbolTableException("No vallid current scope level.");

		// check if id already present on this level
		if (stack.contains(id)
				&& map.get(stack.size() - stack.search(id) + 1).getLevel() == currentLevel)
			throw new SymbolTableException(
					id + " already declared on this scope level.");

		stack.add(id);
		entry.setLevel(currentLevel);
		map.put(stack.size(), entry);
	}

	/**
	 * Get the Entry corresponding with id whose level is the highest; in other
	 * words, that is defined last.
	 * 
	 * @return Entry of this id on the highest level null if this SymbolTable
	 *         does not contain id
	 */
	public Entry retrieve(String id) {
		if (stack.contains(id)) {
			return map.get(stack.size() - stack.search(id) + 1);
		} else
			return null;
	}

	@Override
	public String toString() {
		return "SymbolTable [currentLevel=" + currentLevel + ", stack=" + stack
				+ ", map=" + map + "]";
	}

	public static void main(String[] args) {
		try {

			SymbolTable<IdEntry> test = new SymbolTable<IdEntry>();
			System.out.println(test);
			test.openScope();
			test.enter("aap", new IdEntry());
			System.out.println(test.retrieve("aap").getLevel());
			System.out.println(test);
			test.openScope();
			System.out.println(test);
			test.enter("aap", new IdEntry());
			System.out.println(test.retrieve("aap").getLevel());
			System.out.println(test);
			test.openScope();
			System.out.println(test);
			test.enter("aap", new IdEntry());
			System.out.println(test.retrieve("aap").getLevel());
			System.out.println(test);
			test.closeScope();
			System.out.println(test.retrieve("aap").getLevel());
			test.closeScope();
			System.out.println(test);
			System.out.println(test.retrieve("aap").getLevel());
			test.closeScope();
			System.out.println(test);
		} catch (Exception e) {
			System.err.println(e.getMessage());
		}
	}

}

/** Exception class to signal problems with the SymbolTable */
class SymbolTableException extends Exception {
	/** {@link serialVersionUID} is required for Serializable */
	public static final long serialVersionUID = 24362462L;

	public SymbolTableException(String msg) {
		super(msg);
	}
}
